"""Build DFuse"""
import os
import daos_build
import compiler_setup

HEADERS = ['ioil_io.h', 'ioil_defines.h',
           'ioil_api.h', 'ioil_preload.h',
           'ioil.h']
COMMON_SRC = ['dfuse_obj_da.c',
              'dfuse_vector.c']
DFUSE_SRC = ['dfuse_core.c',
             'dfuse_main.c',
             'dfuse_fuseops.c',
             'dfuse_cont.c',
             'dfuse_thread.c',
             'dfuse_pool.c']
OPS_SRC = ['create',
           'fgetattr',
           'forget',
           'getxattr',
           'listxattr',
           'ioctl',
           'lookup',
           'mknod',
           'open',
           'opendir',
           'read',
           'rename',
           'readdir',
           'readlink',
           'removexattr',
           'setxattr',
           'setattr',
           'symlink',
           'unlink',
           'write',
           'statfs']

IOIL_SRC = ['int_posix.c', 'int_read.c', 'int_write.c']

def build_common(env, files, is_shared):
    """Build the common objects as shared or static"""
    common = []

    for src_file in files:
        if is_shared:
            common += env.SharedObject(src_file, SHOBJPREFIX='s_')
        else:
            common += env.Object(src_file)

    return common

def build_client_libs_shared(env, prereqs):
    """build the shared interception library"""

    ilenv = env.Clone()
    ilenv.AppendUnique(CFLAGS=['-fPIC'])
    ilenv.AppendUnique(CPPDEFINES=['IOIL_PRELOAD'])
    ilenv.AppendUnique(LIBPATH=["../dfs"])
    ilenv.AppendUnique(LIBS=['dfs'])
    penv = ilenv.Clone()
    penv.AppendUnique(CPPDEFINES=['_FILE_OFFSET_BITS=64'])

    il_obj = []
    for src in IOIL_SRC:
        il_obj += ilenv.SharedObject(os.path.join('il', src),
                                     SHOBJPREFIX='s_')
    common = build_common(penv, COMMON_SRC, True)

    # Now build the interception library
    il_lib = daos_build.library(ilenv, 'il/libioil', il_obj + common)
    env.InstallVersionedLib(os.path.join("$PREFIX", 'lib64'), il_lib)
    dfuse_lib = daos_build.library(ilenv, 'common/libdfuse', common)

    gen_script = daos_build.program(ilenv, 'il/gen_script', ['il/gen_script.c'],
                                    LIBS=[])
    if prereqs.test_requested():
        script = ilenv.Command('il/check_ioil_syms', gen_script,
                               "$SOURCE -s $TARGET")
        env.Install('$PREFIX/lib/daos/TESTING/scripts', script)
    script = ilenv.Command('il/ioil-ld-opts', gen_script,
                           '$SOURCE -l $TARGET')
    env.Install('$PREFIX/share/daos', script)
    env.InstallVersionedLib(os.path.join("$PREFIX", 'lib64'), dfuse_lib)

    return dfuse_lib + il_lib

def build_client_libs_static(env, common):
    """build the static interception library"""

    ilenv = env.Clone()
    il_obj = []
    for src in IOIL_SRC:
        il_obj += ilenv.Object(os.path.join('il', src))

    # Now build the interception library
    il_lib_src = ilenv.Library('il/libioil_source', il_obj + common)
    il_lib = ilenv.Command('il/libioil.a', il_lib_src,
                           'objcopy --localize-hidden $SOURCE $TARGET')

    env.Install('$PREFIX/lib64', il_lib)

    # Now build the interception library
    dfuse_lib_src = ilenv.Library('common/libdfuse_source', common)
    dfuse_lib = ilenv.Command('common/libdfuse.a', dfuse_lib_src,
                              'objcopy --localize-hidden $SOURCE $TARGET')
    env.Install('$PREFIX/lib64', dfuse_lib)

    return il_lib + dfuse_lib

def check_struct_member(context, text, struct, member):
    """scons check for a struct member existing"""

    context.Message('Checking for {} in {} '.format(member, struct))

    src = '{0}\n{1} val = {{.{2} = 0}};\nint main() {{}}'.format(text, struct,
                                                                 member)
    rc = context.TryCompile(src, '.c')
    context.Result(rc)
    return rc

def check_ioctl_def(context, ctype):
    """scons check for a struct member existing"""

    context.Message('Checking if fuse ioctl is type {} '.format(ctype))

    src = """#include <fuse3/fuse_lowlevel.h>

extern void
my_ioctl (fuse_req_t req, fuse_ino_t ino, %s cmd,
    void *arg, struct fuse_file_info *fi, unsigned flags,
    const void *in_buf, size_t in_bufsz, size_t out_bufsz);

struct fuse_lowlevel_ops ops = {.ioctl = my_ioctl};

""" % ctype

    rc = context.TryCompile(src, '.c')
    context.Result(rc)
    return rc

def configure_fuse(cenv):
    """Configure for specific version of fuse"""
    if GetOption('help') or GetOption('clean'):
        return

    check = Configure(cenv,
                      custom_tests={'CheckStructMember': check_struct_member,
                                    'CheckFuseIoctl': check_ioctl_def})

    if check.CheckStructMember('#include <fuse3/fuse.h>',
                               'struct fuse_file_info',
                               'cache_readdir'):
        cenv.AppendUnique(CPPDEFINES={'HAVE_CACHE_READDIR':'1'})

    if check.CheckFuseIoctl('unsigned int'):
        pass
    elif check.CheckFuseIoctl('int'):
        cenv.AppendUnique(CPPDEFINES={'FUSE_IOCTL_USE_INT':'1'})
    else:
        print('Could not determine type of fuse ioctl type')
        Exit(2)

    check.Finish()

def scons():
    """Scons function"""

    Import('env', 'prereqs')

    if env['CC'] == 'gcc':
        il_env = env.Clone()
    else:
        Import('base_env')
        il_env = base_env.Clone()
        il_env['CC'] = 'gcc'
        compiler_setup.base_setup(il_env)

    # Set options which are used throughout the src.
    dfuse_env = env.Clone(LIBS=[])
    dfuse_env.AppendUnique(CPPPATH=['.'])
    dfuse_env.AppendUnique(CFLAGS=['-pthread'])
    dfuse_env.AppendUnique(LIBS=['pthread', 'daos', 'daos_common', 'uuid'])

    gcc_env = il_env.Clone(LIBS=[])
    gcc_env.AppendUnique(CPPPATH=['.'])
    gcc_env.AppendUnique(CFLAGS=['-pthread', '-fvisibility=hidden'])
    gcc_env.AppendUnique(LIBS=['pthread', 'daos', 'daos_common'])

    libs = build_client_libs_shared(gcc_env, prereqs)

    static_env = gcc_env.Clone()

    # Build a static library of the common parts.
    common = build_common(dfuse_env, COMMON_SRC, False)
    libs += build_client_libs_static(static_env, common)

    cenv = dfuse_env.Clone()
    cenv.AppendUnique(LIBPATH=["../dfs"])
    cenv.AppendUnique(CPPPATH=["../dfs"])

    cenv.AppendUnique(LIBS=['dfs', 'duns'])

    prereqs.require(cenv, 'fuse')

    configure_fuse(cenv)

    dfuse_obj = []
    for src in DFUSE_SRC:
        dfuse_obj += cenv.Object(src)
    for src in OPS_SRC:
        dfuse_obj += cenv.Object(os.path.join('ops', '%s.c' % src))
    cenv.AppendUnique(LIBS=['gurt'])
    progs = daos_build.program(cenv, 'dfuse/dfuse', common + dfuse_obj)

    Default(progs + libs)

    cenv.Install(os.path.join("$PREFIX", 'bin'), progs)

    # Do not install headers currently.
    # for header in HEADERS:
    #  dfuse_env.Install(os.path.join("$PREFIX", 'include'), 'il/%s' % header)

    Export('dfuse_env')

    if prereqs.test_requested():
        SConscript('test/SConscript')

if __name__ == "SCons.Script":
    scons()
